﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace SharpHomeEngine
{
    public delegate void TagStateChangeDelegate(IChangeSource Source, Int64 Handle, Tag Tag, object Value);
    public delegate void TagStateChangesCompletedDelegate(Request4TagStateChange Request);
    public delegate void TagGetStateCompletedDelegate(Request4TagState Request);
    public delegate void RequestCompletedDelegate(Request Request);
    public delegate void DeviceEventDelegate(Device Device, String Event, Tag Tag, object Value);

    public class StateEngine
    {
        
        
        const string LockString = "StateEngineLockString";
        
        private System.Threading.Thread StateChangeProcessorWorker;

        /// <summary>
        /// Fires when a state change has completed.
        /// </summary>
        public event TagStateChangeDelegate StateChange; 
        public event TagStateChangesCompletedDelegate StateChangesCompleted;
        public event RequestCompletedDelegate RequestCompleted;
        public event TagGetStateCompletedDelegate GetStateCompleted;
        public event DeviceEventDelegate DeviceEvent;

        private Dictionary<String, DeviceInterface> DeviceInterfaces = new Dictionary<string, DeviceInterface>();
        private Dictionary<String, Device> Devices = new Dictionary<string, Device>();
        private Dictionary<String, Tag> Tags = new Dictionary<String, Tag>();

        private Queue<Request> StateChangeRequestQueue = new Queue<Request>();

        #region Constructor and Singleton Stuff

        private static StateEngine MyStateEngine;

        internal static StateEngine Engine
        {
            get
            {
                return MyStateEngine;
            }
        }

        /// <summary>
        /// Singleton. Creates a new state engine or returns the current existing one
        /// </summary>
        /// <returns></returns>
        public static StateEngine CreateStateEngine()
        {
            if (MyStateEngine == null)
            {
                MyStateEngine = new StateEngine();
            }
            return MyStateEngine;
        }

        private StateEngine()
        {

        }

        #endregion

        #region register functions

        internal void RegisterTag(Tag Tag)
        {
            lock (LockString)
            {
                if (!DeviceInterfaces.ContainsKey(Tag.DeviceInterface.Name))
                {
                    DeviceInterfaces.Add(Tag.DeviceInterface.Name, Tag.DeviceInterface);
                }
                if (!Devices.ContainsKey(Tag.Device.Address))
                {
                    Devices.Add(Tag.Device.Address, Tag.Device);
                }
                if (Tags.ContainsKey(Tag.Address))
                {
                    throw new Exception("The tag is already registered");
                }
                else
                {
                    Tags.Add(Tag.Address, Tag);
                }
            }
        }
        #endregion

        #region getfunctions


        /// <summary>
        /// Given a sharphome address. (Interface.Device.Tag). Parse and return the individual tokens.
        /// </summary>
        /// <param name="Address"></param>
        /// <param name="DeviceInterface"></param>
        /// <param name="Device"></param>
        /// <param name="Tag"></param>
        internal static void ParseAddress(String Address, ref String DeviceInterface, ref String Device, ref String Tag)
        {
            Regex Reg = new Regex(@"^([\w]*)\.([\w]*)\.([\w]*)$");
            if (!Reg.IsMatch(Address))
            {
                throw new Exception("Cannot parse the address");
            }
            Match Mat = Reg.Match(Address);
            DeviceInterface = Mat.Groups[1].Value;
            Device = Mat.Groups[2].Value;
            Tag = Mat.Groups[3].Value;
        }

        /// <summary>
        /// Given a sharphome address return a previously registered tag
        /// </summary>
        /// <param name="Address"></param>
        /// <returns></returns>
        public Tag GetTagByAddress(string Address)
        {
            if (!Tags.ContainsKey(Address))
            {
                throw new Exception("No such tag");
            }
            else
            {
                return Tags[Address];
            }
        }

        /// <summary>
        /// Given a sharphome address return the value of a previously registered tag
        /// </summary>
        /// <param name="Address"></param>
        /// <returns></returns>
        public object GetValueByAddress(string Address)
        {
            if (!Tags.ContainsKey(Address))
            {
                throw new Exception("No such tag");
            }
            else
            {
                object X = Tags[Address].ValueObj;
                return X;
            }
        }

        public DeviceInterface GetDeviceInterface(String Name)
        {
            if (this.DeviceInterfaces.ContainsKey(Name))
            {
                return this.DeviceInterfaces[Name];
            }
            else
            {
                return null;
            }
        }

        internal void GetByAddress(string Address, ref DeviceInterface DeviceInterface, ref Device Device, ref Tag Tag)
        {
            lock (LockString)
            {
                String sDeviceInterface = "", sDevice = "", sTag = "";
                ParseAddress(Address, ref sDeviceInterface, ref sDevice, ref sTag);
                if (!DeviceInterfaces.ContainsKey(sDeviceInterface))
                {
                    throw new Exception("No such device interface");
                }
                if (!Devices.ContainsKey(Device.GetDeviceAddress(sDeviceInterface, sDevice)))
                {
                    throw new Exception("No such device");
                }
                if (!Tags.ContainsKey(Tag.GetTagAddress(sDeviceInterface, sDevice, sTag)))
                {
                    throw new Exception("No such tag");
                }
                DeviceInterface = DeviceInterfaces[sDeviceInterface];
                Device = Devices[Device.GetDeviceAddress(sDeviceInterface, sDevice)];
                Tag = Tags[Tag.GetTagAddress(sDeviceInterface, sDevice, sTag)];
            }
        }
#endregion

        #region State Change Processor & Thread

        
        /// <summary>
        /// Starts the State Engine Processor
        /// </summary>
        public void StartStateChangeProcessor()
        {
            System.Threading.ThreadStart Function = new System.Threading.ThreadStart(StateChangeProcessor);
            StateChangeProcessorWorker = new System.Threading.Thread(Function);
            StateChangeProcessorWorker.Start();
        }

        private void StateChangeProcessor(){
            while (true)
            {
                try
                {
                    int Count = 0;
                    lock (LockString)
                    {
                        Count = StateChangeRequestQueue.Count;
                        //Console.WriteLine("StateChangeRequestsQueue = {0}", Count);
                    }

                    if (StateChangeRequestQueue.Count == 0)
                    {
                        System.Threading.Thread.Sleep(1);
                        continue;
                    }
                    Request Request;
                    lock (LockString)
                    {
                        Request = StateChangeRequestQueue.Dequeue();
                    }
                    Request.DoRequest();
                }
                catch (Exception E)
                {

                }
            }
        }
#endregion

        #region state change request queuing and events

        internal void RaiseDeviceEvent(Device Device, String Event, Tag Tag, object Value)
        {
            if (DeviceEvent != null)
            {
                DeviceEvent(Device, Event, Tag, Value);
            }
        }
        
        /// <summary>
        /// to be called when any state change is made
        /// </summary>
        /// <param name="DeviceInterface"></param>
        /// <param name="Device"></param>
        /// <param name="Tag"></param>
        /// <param name="Value"></param>
        internal void RaiseChangeTagState(DeviceInterface DeviceInterface, Device Device, Tag Tag, object Value)
        {
            //Tag.SetValue(Value);
            if (StateChange != null)
            {
                StateChange(DeviceInterface, 0, Tag, Value);
            }
        }

        /// <summary>
        /// To be called when any request is compeleted
        /// </summary>
        /// <param name="Request"></param>
        internal void RaiseRequestCompleted(Request Request)
        {
            if (RequestCompleted != null)
            {
                RequestCompleted(Request);
            }
        }

        /// <summary>
        /// raises the event when any state change request is completed. Controller only
        /// </summary>
        internal void RaiseStateGetCompletedEvent(Request4TagState Request)
        {
            if (GetStateCompleted != null)
            {
                GetStateCompleted(Request);
            }
            RaiseRequestCompleted(Request);
        }

        /// <summary>
        /// raises the event when any state change request is completed. Controller only
        /// </summary>
        internal void RaiseStateChangeCompletedEvent(Request4TagStateChange Request)
        {
            if (StateChangesCompleted != null)
            {
                StateChangesCompleted(Request);
            }
            if (StateChange != null)
            {
                StateChange(Request.Controller, Request.ID, Request.Tag, Request.Value);
            }
            RaiseRequestCompleted(Request);
        }

        //Intended to be used for multiple state changes performed together.
        /// <summary>
        /// Intended to be used for multiple state changes performed together. Not really used yet.
        /// </summary>
        /// <param name="Requests"></param>
        internal void QueueRequest(Request Request)
        {
            lock (LockString)
            {
                StateChangeRequestQueue.Enqueue(Request);
            }
        }

        #endregion
    }
}
